using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using CSScriptLibrary;
using csscript;

public partial class Precompiler : PrecompilerBase
{
#if !CSS_PROJECT
    static public int Main(string[] args)
    {
        return new Precompiler().MainImpl(args);
    }

    public int MainImpl(string[] args)
    {
        //System.Diagnostics.Debug.Assert(false);
        string originalFile = args[0];
        string generatedFile = Path.ChangeExtension(originalFile, ".g.cs");

        ////////////////////////////////////
        //resolve all required file names
        if (args.Length > 1 && !args[1].StartsWith("/primary:"))
            generatedFile = Path.Combine(Path.GetDirectoryName(generatedFile), args[1]);

        if (CSScript.GlobalSettings.HideAutoGeneratedFiles == Settings.HideOptions.HideAll)
        {
            if (Environment.GetEnvironmentVariable("CSScriptDebugging") != null) //running under VS on-flay project
            {
                if (CSSEnvironment.PrimaryScriptFile != null && CSSEnvironment.PrimaryScriptFile != "")
                    generatedFile = Path.Combine(CSSEnvironment.GetCacheDirectory(originalFile), Path.GetFileName(generatedFile));
                else
                {
                    //there will be no CSSEnvironment.PrimaryScriptFile if we are running from VS 
                    //but VS will pass is as an extra arg
                    for (int i = 1; i < args.Length; i++)
                        if (args[i].StartsWith("/primary:"))
                        {
                            string primaryScriptFile = args[i].Substring("/primary:".Length);
                            generatedFile = Path.Combine(CSSEnvironment.GetCacheDirectory(primaryScriptFile), Path.GetFileName(generatedFile));
                            break;
                        }
                }

            }
            else if (Environment.GetEnvironmentVariable("CSScriptRuntime") != null) //running as a script
            {
                generatedFile = Path.Combine(CSSEnvironment.GetCacheDirectory(CSSEnvironment.PrimaryScriptFile), Path.GetFileName(generatedFile));
            }
        }

        if (!Directory.Exists(Path.GetDirectoryName(generatedFile)))
            Directory.CreateDirectory(Path.GetDirectoryName(generatedFile));

        string oldGeneratedCode = "";
        if (File.Exists(generatedFile))
        {
            using (StreamReader sr = new StreamReader(generatedFile))
                oldGeneratedCode = sr.ReadToEnd();
        }
        else
        {
            using (StreamWriter sw = new StreamWriter(generatedFile)) //create dummy just in case if developer did not specify any macros top be replaced
                sw.WriteLine();
        }

        ////////////////////////////////////
        //parse the file with the macros instructions

        //supportedMacros
        string css_extend_str_enum = "css_extend_str_enum";
        string css_extend_class = "css_extend_class";

        using (StreamReader sr = new StreamReader(originalFile))
        using (StreamWriter sw = new StreamWriter(generatedFile))
        {
            string line = "";

            string header = "// AUTO-GENERATED FILE; DO NOT MODIFY\r\n" +
                            "using System;\r\n";
            string footer = "";
            StringBuilder classCode = new StringBuilder();
            StringBuilder moduleCode = new StringBuilder();

            string autoTypeName = null;
            Context context = new Context();
            List<string> tokens = new List<string>();
            while ((line = sr.ReadLine()) != null)
            {
                line = line.Trim();
                if (line.StartsWith("* ")) //VS likes to insert "* " when new line is entered inside of /**/
                    line = line.Substring(1).Trim();

                tokens.Clear();
                tokens.AddRange(line.Split(' '));
                tokens.RemoveAll(delegate(string item) { return item.Trim() == ""; });
                if (tokens.Count >= 3 && tokens[0] == "/*" && tokens[1].StartsWith("css_extend_")) //start of the declaration region (section)
                {
                    // /* css_extend_class public Log 

                    autoTypeName = tokens[tokens.Count - 1];
                    context.autoTypeDeclaration = tokens[1];
                    context.autoTypeName = autoTypeName;
                    context.autoTypeModifier = "";
                    for (int i = 2; i < tokens.Count - 1; i++)
                        context.autoTypeModifier = tokens[i] + " ";
                    context.macros.Clear();

                    classCode.Length = 0;

                    CompileResult result = OnBeforeSection(context);
                    if (result != null)
                    {
                        if (result.insertionPosition == InsertionPosition.ClassBody)
                            classCode.AppendLine(result.code);
                        else if (result.insertionPosition == InsertionPosition.ModuleStart)
                            header += result.code + "\r\n";
                        else if (result.insertionPosition == InsertionPosition.ClassStart)
                            header += result.code + "\r\n";
                    }

                    string[] nameParts = autoTypeName.Split('.');
                    for (int i = 0; i < nameParts.Length - 1; i++)
                    {
                        classCode.AppendLine("namespace " + nameParts[i]);
                        classCode.AppendLine("{");
                        classCode.AppendLine();
                    }

                    //css_extend_class, css_extend_str_enum
                    if (tokens[1] == css_extend_class)
                        classCode.AppendLine(context.autoTypeModifier + "partial class " + nameParts[nameParts.Length - 1]);
                    else if (tokens[1] == css_extend_str_enum)
                    {
                        classCode.AppendLine(context.autoTypeModifier + "enum " + nameParts[nameParts.Length - 1]);
                    }
                    classCode.AppendLine("{");
                }
                else if (autoTypeName != null && line.Replace(" ", "").StartsWith("*/")) //end of the declaration region
                {
                    classCode.AppendLine("}\r\n");
                    string[] nameParts = autoTypeName.Split('.');
                    for (int i = 0; i < nameParts.Length - 1; i++)
                    {
                        classCode.AppendLine("}");
                        classCode.AppendLine();
                    }

                    CompileResult result = OnAfterSection(context);
                    if (result != null)
                    {
                        if (result.insertionPosition == InsertionPosition.ClassBody)
                            classCode.AppendLine(result.code);
                        else if (result.insertionPosition == InsertionPosition.ModuleStart)
                            header += result.code + "\r\n";
                        else if (result.insertionPosition == InsertionPosition.ClassStart)
                            header += result.code + "\r\n";
                    }

                    moduleCode.AppendLine(classCode.ToString());
                    moduleCode.AppendLine();

                    autoTypeName = null;
                }
                else if (autoTypeName != null) //line containing macro definition
                {
                    bool found = false;
                    foreach (string key in macros.Keys)
                    {
                        if (line.StartsWith(key + "(") || line.StartsWith(key + " "))
                        {
                            found = true;
                            string[] macroArgs = ExtractArgs(line.Substring(key.Length));

                            if (!context.macros.ContainsKey(autoTypeName))
                                context.macros[autoTypeName] = new List<string[]>();

                            context.macros[autoTypeName].Add(macroArgs); //save macro definition

                            object[] parameters = new object[] { macroArgs, context };

                            //need to use reflection as methods added/declared by used are unknow
                            CompileResult result = (CompileResult)macros[key].Invoke(this, parameters);

                            if (result.insertionPosition == InsertionPosition.ClassBody)
                                classCode.AppendLine(result.code);
                            else if (result.insertionPosition == InsertionPosition.ModuleStart)
                                header += result.code + "\r\n";
                            else if (result.insertionPosition == InsertionPosition.ClassStart)
                                header += result.code + "\r\n";
                        }
                    }
                    if (!found)
                    {
                        Console.WriteLine("\nerror: the macro \"" + line + "\" cannot be translated.\n");
                        return 1;
                    }
                }
            }

            moduleCode.Insert(0, header + "\r\n");
            moduleCode.AppendLine(footer);

            //if (oldGeneratedCode != moduleCode.ToString())
            sw.Write(moduleCode.ToString()); //for now do always 
        }
        return 0;
    }
#endif


    static Dictionary<string, MethodInfo> macros = new Dictionary<string, MethodInfo>();
    Precompiler()
    {
        string[] arr = new string[0];

        foreach (MethodInfo method in typeof(Precompiler).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
        {
            if (method.ReturnType == typeof(CompileResult))
            {
                ParameterInfo[] parameters = method.GetParameters();
                if (parameters.Length == 2 &&
                    parameters[0].ParameterType == arr.GetType() &&
                    parameters[1].ParameterType == typeof(Context))
                {
                    macros[method.Name] = method;
                }
            }
        }
    }
    static string[] ExtractArgs(string declaration)
    {
        int start = declaration.IndexOf("(");
        int end = declaration.LastIndexOf(")");
        string args = declaration.Substring(start + 1, end - start - 1);
        List<string> retval = new List<string>(args.Split(','));
        for (int i = 0; i < retval.Count; i++)
            retval[i] = retval[i].Trim();

        return retval.ToArray();
    }

}
public class PrecompilerBase
{
    public enum InsertionPosition
    {
        ModuleStart,
        ClassStart,
        ClassBody,
    }
    public class CompileResult
    {
        public CompileResult(string code, InsertionPosition insertionPosition)
        {
            this.code = code;
            this.insertionPosition = insertionPosition;
        }
        public string code;
        public InsertionPosition insertionPosition;
    }
    public class Context
    {
        public string autoTypeName = "";
        public string autoTypeDeclaration = "";
        public string autoTypeModifier = "";
        public Dictionary<string, List<string[]>> macros = new Dictionary<string, List<string[]>>();
    }
    public virtual CompileResult OnBeforeSection(Context context)
    {
        return null;
    }

    public virtual CompileResult OnAfterSection(Context context)
    {
        return null;
    }
}